/*
 *  cpu_driver/regs.c
 *  
 *              CPU Monitor 
 *
 *    Spring Semester.  044800.    Technion.
 *   Copyright (C) 2006  Evgeny Shulman  and Elizabeth Shtrom 
 */


#include <linux/string.h>
#include <linux/errno.h>

#include <asm/page.h>
#include  <asm/io.h>

#include <linux/kernel.h>

#include <linux/mm.h>

#include <asm/current.h>

#include "regs.h"
#include "../cpu_monitor.h"
#include "../cpu_consts.h"
#include "../cpu_parse_display/cpu_display.h"


 
void get_gdt_table( void * gdt_table ){
	
	struct Xgt_desc_struct gdtr;
	cm_desc_t * gdt;
	
	__asm__ ("sgdt %0":"=m" (gdtr));
	gdt = (cm_desc_t *) gdtr.address;
	PDEBUG("get_gdt_table:gdtr.address = 0x%lx, gdtr.size = 0x%x\n", gdtr.address, gdtr.size );
	
	//display_gdt( gdt, 0,GDT_ENTRIES);
	memcpy( gdt_table, (void*) gdt , GDT_SIZE );
	return;
}
 
void get_idt_table( void * idt_table ){
	 struct Xgt_desc_struct idtr;
	 
	 __asm__ ("sidt %0":"=m" (idtr));
	memcpy( idt_table, (void*) idtr.address , IDT_SIZE );
	
	return;
}


void get_pgd_table( void * pgd ){

  unsigned long cr3;
  unsigned long * addr ;
  pgd_indirect *pgd_indir = (pgd_indirect *)pgd;
  
  __asm__ ("mov %%cr3,%0\n\t":"=r"(cr3));
 addr = (unsigned long *) phys_to_virt(cr3 & PAGE_MASK);
 //display_pgdir( addr , 700 , 10 );
 pgd_indir->pgd_table = addr;
 
 }
 
 
int get_pt_table( unsigned long pde , unsigned long ** pt_table ){
	
	if (!( pde & _PAGE_PRESENT )){
		PDEBUG("get_pt_table:not present\n" );
		return -ENTRY_NOT_PRESENT;
	}
		
	if (pde & _PAGE_PSE){
		PDEBUG("get_pt_table:the entry doesn't point to a ptable\n" );
		return -PDE_POINTS_TO_PAGE;
	}

	*pt_table =(unsigned long *)phys_to_virt( pde & PAGE_MASK );
//	display_ptable( *pt_table , 0 , 1024 );		

 return 0;
}


int get_ldt_table( void * ldt_table )
{
	
	struct Xgt_desc_struct gdtr;
	unsigned short ldtr_segsel = 0;
	cm_desc_t * ldt, *ldt_desc;
	int ldt_size;
	
	
	/* sldt gives the GDT selector for the segment containing LDT */
	__asm__ __volatile__ ("sgdt %0\n\t" : "=m"(gdtr));
	__asm__ __volatile__ ("sldt %0\n\t" : "=m"(ldtr_segsel));

	if (ldtr_segsel > gdtr.size+1) {
			PDEBUG("invalid ldtr\n");
			return -1;
	}
	PDEBUG("get_ldt_table:ldtr_segsel = 0x%hx\n",ldtr_segsel );
	
	ldt_desc = (cm_desc_t *)(gdtr.address + (ldtr_segsel & ~7));  /* the 3 lsb bits of SegSel are the - TI & RPL attribute fields */
	ldt      = (cm_desc_t *) CM_SEG_DESC_BASE(ldt_desc);
	ldt_size = CM_SEG_DESC_LIMIT(ldt_desc)+1;

	PDEBUG("get_ldt_table:ldt = %p  size = %d bytes \n",ldt,ldt_size );
	
	if(! ldt_size )
		return -NO_LDT;		
	
//	display_ldt  ( ldt ,0 , ldt_size/sizeof(cm_desc_t) );

	memcpy( ldt_table, (void *)ldt , ldt_size );
	
  PDEBUG("get_ldt_table: %d bytes read from ldt\n" ,ldt_size );

	return 0;
}



void get_tss_table( void * tss_table ){
	struct Xgt_desc_struct Xdtr;
	unsigned short tr = 0;
	cm_desc_t * tss, *tss_desc;
	int tss_size;
	struct tss_struct  *tss_kernel;
	
	/* sldt gives the GDT selector for the segment containing LDT */
	__asm__ ("str %0":"=m" (tr));
	
	__asm__  ("sgdt %0\n\t" : "=m"(Xdtr));

	if (tr > Xdtr.size+1) {
			PDEBUG("invalid tr :tr = 0x%hx sgdt size=Ox%hx  adress=%p\n",tr,   Xdtr.size,(void*) Xdtr.address );
			return ;
	}
	
	tss_desc = (cm_desc_t *)(Xdtr.address + (tr & ~7));
	
	tss_size =(CM_SEG_DESC_LIMIT(tss_desc)+1);
	tss      = (cm_tss_struct *) CM_SEG_DESC_BASE(tss_desc);
	
	tss_kernel = ( struct tss_struct  *)CM_SEG_DESC_BASE((tss_desc));
	display_tss(tss_kernel);
	PDEBUG("get_tss_table:tss= %p tr = 0x%hx  size = %d bytes \n",tss,tr,tss_size );
	if(tss_size )
		memcpy( tss_table, (void *) tss , TSS_SIZE );
	return;
}


void init_tr_ldtr( tr_ldtr_s * var){
	var->SegSel=0;
	var->Base=0;
	var->SegLimit=0;
}

void get_regs ( void * regs_void_pointer ){
	
		regs_p X;
	struct Xgt_desc_struct gdtr;
	cm_desc_t *ldt_desc , *tr_desc;
	tr_ldtr_s ldtr, tr;
	int pid = current->pid;
	 PDEBUG ("get_regs: PID = %d \n\n" ,pid);
 	init_tr_ldtr(&tr);
	init_tr_ldtr(&ldtr);
		
	X =( regs_s* )regs_void_pointer;
	
	PDEBUG( "get_regs: start \n");
	
	__asm__ ("sgdt %0":"=m" (X->gdtr));
	__asm__ ("sidt %0":"=m" (X->idtr));
	
	
	__asm__ ("movl %%cr0,%0\n\t"
		 "mov %%cr2,%1\n\t"
		 "mov %%cr3,%2\n\t"
		 "mov %%cr4,%3\n\t"
		:"=r"(X->cr0),"=r"(X->cr2),"=r"(X->cr3),"=r"(X->cr4));

	__asm__ ("mov %%dr0,%0\n\t"
			 "mov %%dr1,%1\n\t"
			 "mov %%dr2,%2\n\t"		 
			 "mov %%dr3,%3\n\t"
			:"=r"(X->dr0),"=r"(X->dr1),"=r"(X->dr2),"=r"(X->dr3));
	__asm__ (
			"mov %%dr4,%0\n\t"
		 	"mov %%dr5,%1\n\t"
		 	"mov %%dr6,%2\n\t"
	   	"mov %%dr7,%3\n\t"
		:"=r"(X->dr4),"=r"(X->dr5),"=r"(X->dr6),"=r"(X->dr7));

	__asm__ ("mov %%cs,%0\n\t"
		 "mov %%es,%1\n\t"
		 "mov %%ss,%2\n\t"
	   "mov %%ds,%3\n\t"
		 "mov %%fs,%4\n\t"
	   "mov %%gs,%5\n\t"
		:"=r"(X->cs),"=r"(X->es),"=r"(X->ss),"=r"(X->ds),"=r"(X->fs),"=r"(X->gs));
			
	/* reading the tr & ldtr registers needs a more comlex approach  */
  
  __asm__ ("sgdt %0\n\t" : "=m" (gdtr));
  
  /* sldt gives the GDT selector for the segment containing LDT */	
	__asm__ ("sldt %0":"=m" (ldtr.SegSel));
  
  if (ldtr.SegSel <= gdtr.size+1) {
		ldt_desc      = (cm_desc_t *)(gdtr.address + (ldtr.SegSel & ~7));  /* the 3 lsb bits of SegSel are the - TI & RPL attribute fields */
		ldtr.Base     = CM_SEG_DESC_BASE(ldt_desc);
		ldtr.SegLimit = CM_SEG_DESC_LIMIT(ldt_desc)+1;
  } /* ldtr invalid in case ldtr.SegSel > gdtr.size+1 since then it exceeds the gdt boundaries */
  
	X->ldtr.SegSel   = ldtr.SegSel  ;
	X->ldtr.Base     = ldtr.Base    ;
	X->ldtr.SegLimit = ldtr.SegLimit;
	
	__asm__ ("str  %0":"=m" (tr.SegSel));
	
	if (tr.SegSel <= gdtr.size+1) {
		tr_desc      = (cm_desc_t *)(gdtr.address + (tr.SegSel & ~7)); /* the 3 lsb bits of SegSel are the - TI & RPL attribute fields */
		tr.Base    = CM_SEG_DESC_BASE(tr_desc);
		tr.SegLimit = CM_SEG_DESC_LIMIT(tr_desc)+1;
	} /* tr invalid in case tr.SegSel > gdtr.size+1 since then it exceeds the gdt boundaries */
	
	X->tr.SegSel   = tr.SegSel  ;
	X->tr.Base     = tr.Base    ;
	X->tr.SegLimit = tr.SegLimit;
	
}

void get_cpuid(void * cpuid_regs)
{
   cpuid_s *buf = (cpuid_s * ) cpuid_regs;
   unsigned long op =  buf->op;
    __asm__("cpuid"														
            : "=a" (buf->eax),														
              "=b" (buf->ebx),														
              "=c" (buf->ecx),														
              "=d" (buf->edx)														
            :"a" (op) ,  "c"(0));	
   PDEBUG("get_cpuid: op = %lx\n" , op  ) ;         			
}



